#include <stdio.h>
#include <unistd.h>
#include <time.h>
#include <stdlib.h>
#include "world.h"
#include "core.h"

/* coler display only for Linux */
#define COLOR_START_PURPLE "\033[45;36m"
#define COLOR_START_GREEN "\033[106;36m"
#define COLOR_START_RED "\033[41;36m"
#define COLOR_END "\033[0m"
#define DISPLAY_CLEAR "\33[2J"

UNIT g_unit[WORLD_BOUNDARY][WORLD_BOUNDARY] = {0};

void world_init(void)
{
    for (int i = 0; i < WORLD_BOUNDARY; i++) {
        for (int j = 0; j < WORLD_BOUNDARY; j++) {
            if (i == 0 || i == WORLD_BOUNDARY - 1) {
                g_unit[i][j].type = BOUNDARY;
                g_unit[i][j].data = '-';
            } else if (j == 0 || j == WORLD_BOUNDARY - 1) {
                g_unit[i][j].type = BOUNDARY;
                g_unit[i][j].data = '|';
            } else {
                g_unit[i][j].type = NONE;
                g_unit[i][j].data = ' ';
            }
        }
    }
}

void world_step(void)
{
    struct timespec cur_time = {0, 0};
    clock_gettime(CLOCK_REALTIME, &cur_time);
    srand((unsigned int )cur_time.tv_nsec);
    int i = rand();
	i %= WORLD_BOUNDARY;

    int j = rand();
	j %= WORLD_BOUNDARY;

    if (g_unit[i][j].type == NONE && g_unit[i][j].data == ' ') {
        g_unit[i][j].type = FOOD;
        g_unit[i][j].data = '*';
    }
}

BODY_ITEM *gp_body;

void world_display(void)
{
    printf(DISPLAY_CLEAR);

    static int tmp = 0;

    for (int i = 0; i < WORLD_BOUNDARY; i++) {
        for (int j = 0; j < WORLD_BOUNDARY; j++) {
            if (g_unit[i][j].type == BODY) {
                printf("%s%c%s", COLOR_START_GREEN, g_unit[i][j].data, COLOR_END);
                tmp++;
                if (tmp > 2) {
                    tmp = 0;
                    gp_body->energy--;
                }
                if (gp_body->energy <= 0 || gp_body->energy > BODY_ENERGY_MAX) {
                    g_unit[i][j].type = DEAD_BODY;
                    g_unit[i][j].data = '%';
                    g_unit[i][j].is_reveal = FALSE;
                }
            } else if (g_unit[i][j].type == DEAD_BODY)
                printf("%s%c%s", COLOR_START_RED, g_unit[i][j].data, COLOR_END);
            else if (g_unit[i][j].is_reveal)
                printf("%s%c%s", COLOR_START_PURPLE, g_unit[i][j].data, COLOR_END);
            else
                printf("%c", g_unit[i][j].data);
        }
        printf("\n");
    }

    static int k = 0;
    k++;
    k %= WORLD_BOUNDARY;
    for (int i = 0; i < k; i++)
        printf(".");
    printf("\n");
    fflush(stdout);
    usleep(100 * 1000);
}

void world_exit(void)
{
}

void world_add_body(BODY_ITEM *body)
{
    gp_body = body;
    world_body_peep(body);
}

void world_body_peep(BODY_ITEM *body)
{
    if (body->x >= WORLD_BOUNDARY)
        return;
    else if (body->y >= WORLD_BOUNDARY)
        return;

    /* 1. cell output */
    cells[CELLS_TOTAL_COUNT - 1].param.sns_out_count;
    int move_drt = cells[CELLS_TOTAL_COUNT - 1].param.sns_out[0]->transmission; // move direction(or not move)
    int peep_drt = cells[CELLS_TOTAL_COUNT - 1].param.sns_out[1]->transmission; // peep direction
    if (peep_drt == MOTIONLESS)
        peep_drt = TOP;
    body->direction = peep_drt;

    if (body->energy <= 0) {
        g_unit[body->x][body->y].type = DEAD_BODY;
        g_unit[body->x][body->y].data = '%';
        g_unit[body->x][body->y].is_reveal = FALSE;
        return;
    } else {
        g_unit[body->x][body->y].type = BODY;
        g_unit[body->x][body->y].data = '@';
        g_unit[body->x][body->y].is_reveal = FALSE;
    }

    /* 2. peep clear */
    switch (body->last_direct) {
    case MOTIONLESS: // drop
    case TOP:
        if (body->x - 2 >= 0) {
            g_unit[body->x - 2][body->y].is_reveal = FALSE;
        }
        g_unit[body->x - 1][body->y].is_reveal = FALSE;
        break;
    case BOTTOM:
        if (body->x + 2 <= WORLD_BOUNDARY - 1) {
            g_unit[body->x + 2][body->y].is_reveal = FALSE;
        }
        g_unit[body->x + 1][body->y].is_reveal = FALSE;
        break;
    case LEFT:
        if (body->y - 2 >= 0) {
            g_unit[body->x][body->y - 2].is_reveal = FALSE;
        }
        g_unit[body->x][body->y - 1].is_reveal = FALSE;
        break;
    case RIGHT:
        if (body->y + 2 <= WORLD_BOUNDARY - 1) {
            g_unit[body->x][body->y + 2].is_reveal = FALSE;
        }
        g_unit[body->x][body->y + 1].is_reveal = FALSE;
        break;
    case TOP_LEFT: //TODO: add more
        break;
    case TOP_RIGHT:
        break;
    case BOTTOM_LEFT:
        break;
    case BOTTOM_RIGHT:
        break;
    default:
        break;
    }

    /* 3. move */
    switch (move_drt) {
    case MOTIONLESS:
        break;
    case TOP:
        g_unit[body->x][body->y].type = NONE;
        g_unit[body->x][body->y].data = ' ';
        if (body->x - 1 > 0) {
            body->x--;
            if (g_unit[body->x][body->y].type == FOOD)
                gp_body->energy++;
        }
        g_unit[body->x][body->y].type = BODY;
        g_unit[body->x][body->y].data = '@';
        break;
    case BOTTOM:
        g_unit[body->x][body->y].type = NONE;
        g_unit[body->x][body->y].data = ' ';
        if (body->x + 1 < WORLD_BOUNDARY - 1) {
            body->x++;
            if (g_unit[body->x][body->y].type == FOOD)
                gp_body->energy++;
        }
        g_unit[body->x][body->y].type = BODY;
        g_unit[body->x][body->y].data = '@';
        break;
    case LEFT:
        g_unit[body->x][body->y].type = NONE;
        g_unit[body->x][body->y].data = ' ';
        if (body->y - 1 > 0) {
            body->y--;
            if (g_unit[body->x][body->y].type == FOOD)
                gp_body->energy++;
        }
        g_unit[body->x][body->y].type = BODY;
        g_unit[body->x][body->y].data = '@';
        break;
    case RIGHT:
        g_unit[body->x][body->y].type = NONE;
        g_unit[body->x][body->y].data = ' ';
        if (body->y + 1 < WORLD_BOUNDARY - 1) {
            body->y++;
            if (g_unit[body->x][body->y].type == FOOD)
                gp_body->energy++;
        }
        g_unit[body->x][body->y].type = BODY;
        g_unit[body->x][body->y].data = '@';
        break;
    case TOP_LEFT:      // drop
    case TOP_RIGHT:     // drop
    case BOTTOM_LEFT:   // drop
    case BOTTOM_RIGHT:  // drop
    default:
        break;
    }

    /* 4. peep */
    switch (body->direction) {
    case MOTIONLESS: // drop
    case TOP:
        body->data_far = BOUNDARY;
        if (body->x - 2 >= 0) {
            g_unit[body->x - 2][body->y].is_reveal = TRUE;
            body->data_far = g_unit[body->x - 2][body->y].type;
        }
        g_unit[body->x - 1][body->y].is_reveal = TRUE;
        body->data_near = g_unit[body->x - 1][body->y].type;
        break;
    case BOTTOM:
        body->data_far = BOUNDARY;
        if (body->x + 2 <= WORLD_BOUNDARY - 1) {
            g_unit[body->x + 2][body->y].is_reveal = TRUE;
            body->data_far = g_unit[body->x + 2][body->y].type;
        }
        g_unit[body->x + 1][body->y].is_reveal = TRUE;
        body->data_near = g_unit[body->x + 1][body->y].type;
        break;
    case LEFT:
        body->data_far = BOUNDARY;
        if (body->y - 2 >= 0) {
            g_unit[body->x][body->y - 2].is_reveal = TRUE;
            body->data_far = g_unit[body->x][body->y - 2].type;
        }
        g_unit[body->x][body->y - 1].is_reveal = TRUE;
        body->data_near = g_unit[body->x][body->y - 1].type;
        break;
    case RIGHT:
        body->data_far = BOUNDARY;
        if (body->y + 2 <= WORLD_BOUNDARY - 1) {
            g_unit[body->x][body->y + 2].is_reveal = TRUE;
            body->data_far = g_unit[body->x][body->y + 2].type;
        }
        g_unit[body->x][body->y + 1].is_reveal = TRUE;
        body->data_near = g_unit[body->x][body->y + 1].type;
        break;
    case TOP_LEFT: //TODO: add more
        break;
    case TOP_RIGHT:
        break;
    case BOTTOM_LEFT:
        break;
    case BOTTOM_RIGHT:
        break;
    default:
        break;
    }
    body->last_direct = body->direction;

    /* 5. cell input */
    cells[0].param.sns_in_count = 4;
    cells[0].param.sns_in[0]->transmission = body->direction;
    cells[0].param.sns_in[1]->transmission = body->energy;
    cells[0].param.sns_in[2]->transmission = body->data_near;
    cells[0].param.sns_in[3]->transmission = body->data_far;
}
